# Author: Valley-e
# Date: 2025/1/11  
# Description:

from pptflow.utils import mylogger, setting_dic as sd

logger = mylogger.get_logger(__name__)
logger.info("Loaded mylogger, and setting_dic")
from pptflow.utils.datapath import resource_path

import os

import json

logger.info("Loaded json")

import platform

logger.info("Loaded platform")

import shutil

logger.info("Loaded shutil")

import sys

logger.info("Loaded sys")

import re

logger.info("Loaded re")

import threading

logger.info("Loaded threading")

import customtkinter as ctk

logger.info("Loaded customtkinter")

from tkinter import PhotoImage

logger.info("Loaded PhotoImage")

from tkinter import filedialog, messagebox

logger.info("Loaded filedialog and messagebox")

from PIL import Image

logger.info("Loaded PIL.Image")

from pptflow.gui.custom_tooltip import CustomTooltip

logger.info("Loaded CustomTooltip")

from pptflow.tts.tts_service_factory import get_tts_service

logger.info("Loaded get_tts_service")

from pptflow.config.setting_factory import get_default_setting

logger.info("Loaded get_default_setting")


class PPTFlowApp(ctk.CTk):
    def __init__(self):
        super().__init__()
        # Load environment variables first
        from dotenv import load_dotenv
        load_dotenv()  # This will load the .env file
        
        # Get TTS provider from environment variables
        tts_provider = os.getenv("TTS_SERVICE_PROVIDER", "kokoro").lower()
        logger.info(f"Initializing with TTS provider: {tts_provider}")
        
        # Initialize Setting with the TTS provider
        self.setting = get_default_setting(
            os_name=platform.system(),
            language=os.getenv("LANGUAGE", "en").lower(),
            tts_service_provider=tts_provider
        )
        
        # Initialize TTS service
        self.tts = self.load_tts(tts_provider)
        # 初始化界面语言
        self.language_modes = get_locales_subdirectories() if len(
            get_locales_subdirectories()) > 0 else sd.language_mode
            
        # 获取系统语言并设置默认语言
        system_lang = os.getenv('LANGUAGE', '').lower() or os.getenv('LANG', 'en').split('_')[0].lower()
        self.current_language = system_lang if system_lang in self.language_modes else 'en'
        logger.info(f"System language: {system_lang}, Using language: {self.current_language}")
        self._translations_cache = {}  # Add translation cache
        self.translations = self.get_translation()
        # 初始化 CustomTkinter
        self.theme = "light"
        ctk.set_appearance_mode(self.theme)
        ctk.set_default_color_theme("blue")

        self.step = 0
        self.labels = ["Select PPT File", "Adjust Settings", "Generate Video", "Preview and Play"]
        self.icons = ["pptfile.png", "settings.png", "generate.png", "play.png"]
        self.disabled_icons = ["pptfile-disabled.png", "settings-disabled.png", "generate-disabled.png",
                               "play-disabled.png"]
        self.arrow_icons = ["arrow.png", "arrow-disabled.png"]
        # Get icon directory path
        self.icon_dir = resource_path(os.path.join("assets", "icons"))
        self.iconphoto(False, PhotoImage(file=os.path.join(self.icon_dir, "pptflow.png")))

        self.loading_title = self.get_text("generate_video")
        self.file_display = ""
        self.tooltip = None
        self.cancel_file = None
        self.select_button = None
        self.adjust_settings = None
        self.cancel_settings = None
        self.generate_button = None

        # Configure window
        self.title("PPTFlow")
        self.center_window()

        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        # Create main frame
        self.main_frame = ctk.CTkFrame(self, border_width=0, corner_radius=0, fg_color="transparent")
        self.main_frame.grid(row=0, column=0, sticky="nsew")
        self.main_frame.grid_columnconfigure(0, weight=1)
        self.main_frame.grid_rowconfigure(0, weight=1)
        self.main_frame.grid_rowconfigure(1, weight=3)
        self.main_frame.grid_rowconfigure(2, weight=1)

        # Load and set background image
        bg_image = ctk.CTkImage(Image.open(os.path.join(self.icon_dir, "background2.png")), size=(800, 173))
        self.bg_label = ctk.CTkLabel(self.main_frame, image=bg_image, text="")
        self.bg_label.place(x=0, y=0, anchor="nw")

        # 顶部标题和图标部分
        self.create_top_section()

        # 中间流程图布局
        self.create_workflow_section()
        logger.info('initiated PPTFlowApp')

    def create_top_section(self):
        # 右上角的图标按钮（设置和GitHub）
        self.settings_button = ctk.CTkButton(self.main_frame, text="",
                                             image=self.load_ctk_image(
                                                 os.path.join(self.icon_dir, "system-setting.png"),
                                                 size=20),
                                             width=20, height=20, fg_color="#0066FF", corner_radius=0,
                                             hover=False,
                                             command=lambda: self.select_frame("System Settings"))
        self.help_button = ctk.CTkButton(self.main_frame, text="",
                                         image=self.load_ctk_image(os.path.join(self.icon_dir, "help.png"),
                                                                   size=20),
                                         width=20, height=20, fg_color="#0066FF", corner_radius=0,
                                         hover=False,
                                         command=lambda: self.on_button_click("Website"))

        self.settings_button.grid(row=0, column=1, padx=0, pady=10, sticky="ne")
        self.help_button.grid(row=0, column=2, padx=(0, 5), pady=10, sticky="ne")

    def create_workflow_section(self):
        self.flow_frame = ctk.CTkFrame(self.main_frame, corner_radius=0, fg_color="white")
        self.flow_frame.grid(row=1, column=0, columnspan=7, padx=100, pady=(40, 60), sticky="nsew")
        for i in range(7):
            self.flow_frame.grid_columnconfigure(i, weight=1)
        self.icon_frame = ctk.CTkFrame(self.flow_frame, corner_radius=0, fg_color="transparent")
        self.icon_frame.grid(row=0, column=0, columnspan=7, sticky="nsew")
        for i in range(7):
            self.icon_frame.grid_columnconfigure(i, weight=1)
        self.button_frame = ctk.CTkFrame(self.flow_frame, corner_radius=0, border_width=0, fg_color="transparent")
        self.button_frame.grid(row=1, column=0, columnspan=7, sticky="nsew")

        # 流程图布局
        self.select_flow_0(0)
        self.setting_flow_1(1)
        self.generation_flow_2(2)
        self.review_flow_3(3)

    def select_flow_0(self, i, row_offset=1):
        icon_image, arrow_image = self.update_image(i)
        pptfile_icon = ctk.CTkButton(self.icon_frame, image=icon_image, text="", width=40, height=60,
                                     fg_color="transparent", hover=False)
        pptfile_icon.grid(row=0, column=i * 2, padx=(60, 0), pady=(50, 0))
        arrow = ctk.CTkButton(self.icon_frame, text='', width=50, height=20, image=arrow_image,
                              fg_color="transparent", hover=False)
        arrow.grid(row=0, column=i * 2 + 1, pady=(50, 0))
        self.file_label_var = ctk.StringVar()
        self.file_label = ctk.CTkLabel(self.button_frame, textvariable=self.file_label_var,
                                       font=ctk.CTkFont(size=12), width=100)
        self.file_label.grid(row=row_offset + 1, column=i * 2, padx=(40, 5), pady=5)

        self.cancel_file = ctk.CTkButton(self.button_frame, text=self.get_text("cancel"),
                                         font=ctk.CTkFont(size=12), width=110, border_width=1, hover=False,
                                         fg_color="transparent", text_color="#2563EB", border_color="#2563EB",
                                         command=lambda: self.on_button_click("Cancel File"))
        self.cancel_file.grid(row=row_offset + 2, column=i * 2, padx=(40, 5), pady=5)
        self.file_label.grid_remove()
        self.cancel_file.grid_remove()

        self.select_button = ctk.CTkButton(self.button_frame, text=self.get_text("select_ppt"),
                                           font=ctk.CTkFont(size=12), width=110, hover_color="#1D4ED8",
                                           command=lambda: self.on_button_click("Select PPT File"))
        self.select_button.grid(row=row_offset + 1, column=i * 2, padx=(40, 5), pady=5)
        self.update_button(i, self.select_button)

    def setting_flow_1(self, i, row_offset=1):
        icon_image, arrow_image = self.update_image(i)
        setting_icon = ctk.CTkButton(self.icon_frame, image=icon_image, text="", width=65,
                                     height=65, fg_color="transparent", hover=False)
        setting_icon.grid(row=0, column=i * 2, padx=0, pady=(50, 0))
        arrow = ctk.CTkButton(self.icon_frame, text='', width=50, height=20, image=arrow_image,
                              fg_color="transparent", hover=False)
        arrow.grid(row=0, column=i * 2 + 1, pady=(50, 0))
        self.adjust_button = ctk.CTkButton(self.button_frame, text=self.get_text("adjust_settings"),
                                           font=ctk.CTkFont(size=12), width=110,
                                           text_color="white", text_color_disabled="white",
                                           command=lambda: self.select_frame("Adjust Settings"))
        self.skip_button = ctk.CTkButton(self.button_frame, text=self.get_text("skip_settings"),
                                         font=ctk.CTkFont(size=12), width=110,
                                         text_color="white", text_color_disabled="white",
                                         command=lambda: self.on_button_click("Skip Settings"))
        self.adjust_button.grid(row=row_offset + 1, column=i * 2, padx=(20, 20), pady=5)
        self.skip_button.grid(row=row_offset + 2, column=i * 2, padx=(20, 20), pady=5)
        self.cancel_settings = ctk.CTkButton(self.button_frame, text=self.get_text("cancel"),
                                             font=ctk.CTkFont(size=12), width=110, border_width=1, hover=False,
                                             fg_color="transparent", text_color="#2563EB", border_color="#2563EB",
                                             command=lambda: self.on_button_click("Cancel Settings"))
        self.cancel_settings.grid(row=row_offset + 3, column=i * 2, pady=5, padx=(5, 5))
        self.cancel_settings.grid_remove()
        self.update_button(i, self.adjust_button)
        self.update_button(i, self.skip_button)

    def generation_flow_2(self, i, row_offset=1):
        icon_image, arrow_image = self.update_image(i)

        generate_icon = ctk.CTkButton(self.icon_frame, image=icon_image, text="", width=65,
                                      height=65, fg_color="transparent", hover=False)
        generate_icon.grid(row=0, column=i * 2, padx=0, pady=(50, 0))
        arrow = ctk.CTkButton(self.icon_frame, text='', width=50, height=20, image=arrow_image,
                              fg_color="transparent", hover=False)
        arrow.grid(row=0, column=i * 2 + 1, pady=(50, 0))

        self.progress_bar = ctk.CTkProgressBar(self.button_frame, width=100, height=15, corner_radius=0,
                                               bg_color="transparent", progress_color="#2563EB")
        self.progress_bar.grid(row=row_offset + 1, column=i * 2, padx=5, pady=5)
        self.progress_bar.set(0)

        # Hide progress frame initially
        self.progress_bar.grid_remove()

        self.generate_button = ctk.CTkButton(self.button_frame, text=self.get_text("generate_video"),
                                             font=ctk.CTkFont(size=12), width=110, text_color="white",
                                             text_color_disabled="white",
                                             command=lambda label="Generate Video": self.on_button_click(label))
        self.generate_button.grid(row=row_offset + 1, column=i * 2, pady=5, padx=5)
        self.update_button(i, self.generate_button)

    def review_flow_3(self, i, row_offset=1):
        icon_image, arrow_image = self.update_image(i)
        review_icon = ctk.CTkButton(self.icon_frame, image=icon_image, text="", width=65,
                                    height=65, fg_color="transparent", hover=False)
        review_icon.grid(row=0, column=i * 2, padx=(0, 60), pady=(50, 0))
        self.play_button = ctk.CTkButton(self.button_frame, text=self.get_text("preview_and_play"),
                                         font=ctk.CTkFont(size=12), width=110,
                                         text_color="white", text_color_disabled="white",
                                         command=lambda: self.on_button_click("Preview and Play"))
        self.open_button = ctk.CTkButton(self.button_frame, text=self.get_text("open_dir"),
                                         font=ctk.CTkFont(size=12), width=110,
                                         text_color="white", text_color_disabled="white",
                                         command=lambda: self.on_button_click("Open Directory"))
        self.reselect_button = ctk.CTkButton(self.button_frame, text=self.get_text("reselect_ppt"),
                                             font=ctk.CTkFont(size=12), width=110, border_width=1, hover=False,
                                             fg_color="transparent", text_color="#2563EB", border_color="#2563EB",
                                             command=lambda: self.on_button_click("Reselect PPT"))
        self.play_button.grid(row=row_offset + 1, column=i * 2, pady=5, padx=(20, 40))
        self.open_button.grid(row=row_offset + 2, column=i * 2, pady=5, padx=(20, 40))
        self.reselect_button.grid(row=row_offset + 3, column=i * 2, pady=5, padx=(20, 40))
        self.update_button(i, self.play_button)
        self.update_button(i, self.open_button)
        self.reselect_button.grid_remove()

    def update_button(self, icon_index, button):
        # 根据步骤更新按钮状态
        if icon_index < self.step:
            # 完成的步骤
            button.configure(state="disabled", fg_color="#B7B7B7", text_color="white")
        elif icon_index == self.step:
            # 当前步骤
            button.configure(state="normal", fg_color="#2563EB", text_color="white", hover_color="#1D4ED8")
        else:
            # 后续步骤，禁用
            button.configure(state="disabled", fg_color="#B7B7B7", text_color="white")

    def update_image(self, icon_index):
        # 根据步骤更新图标
        if icon_index <= self.step:
            # 完成的步骤
            icon_image = self.load_ctk_image(self.icons[icon_index], size=60)
            arrow_image = self.load_ctk_image(self.arrow_icons[0], size=(50, 16))
        else:
            # 后续步骤，禁用
            icon_image = self.load_ctk_image(self.disabled_icons[icon_index], size=60)
            arrow_image = self.load_ctk_image(self.arrow_icons[1], size=(50, 16))
        return icon_image, arrow_image

    def prev_step(self):
        logger.info(f"Current Step: {self.step}")
        if self.step > 0:
            self.step -= 1
            self.update_flow()
        logger.info(f"Go previous Step: {self.step}")

    def next_step(self):
        logger.info(f"Current Step: {self.step}")
        if self.step == 0 and self.file_display == "":
            messagebox.showerror("Error", "Please select a PPT file before proceeding.")
            return
        if self.step < len(self.icons) - 1:
            self.step += 1
            self.update_flow()
        logger.info(f"Go next Step: {self.step}")

    def update_flow(self):
        self.select_flow_0(0)
        self.setting_flow_1(1)
        self.generation_flow_2(2)
        self.review_flow_3(3)

    def center_window(self):
        """Center the window on the screen."""
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        window_width = 800
        window_height = 450
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2
        self.geometry(f"{window_width}x{window_height}+{x}+{y}")

        self.resizable(False, False)

    def get_text(self, key):
        """
        Get translated text for the given key.
        
        Args:
            key (str): The translation key
            
        Returns:
            str: The translated text, or the key itself if translation is not found
        """
        if not key:
            logger.warning("Empty translation key provided")
            return ""
            
        # Try to get the translation
        try:
            # First try the current language
            if key in self.translations and self.translations[key]:
                return self.translations[key]
                
            # If not found and we're not in English, try English as fallback
            if self.current_language != 'en' and 'en' in self.language_modes:
                en_translations = self._get_english_translations()
                if key in en_translations and en_translations[key]:
                    logger.warning(f"Using English fallback for key: {key}")
                    return en_translations[key]
            
            # If still not found, log a warning and return the key
            logger.warning(f"Translation not found for key: {key}")
            return key
            
        except Exception as e:
            logger.error(f"Error getting translation for key '{key}': {e}")
            return key

    def text_to_key(self, text):
        """Convert display text back to key"""
        for key, value in self.translations.items():
            if value == text:
                return key
        return text

    def change_language(self, language):
        if language in self.language_modes:
            self.current_language = language
            logger.info(f"Changing language to: {self.current_language}")
            self.translations = self.get_translation()
            # Update UI elements that might have hardcoded text
            self.update_language()
            
            # Update window title
            self.title(self.get_text("app_title") if "app_title" in self.translations else "PPTFlow")
            
            # Update any other UI elements that need to be refreshed
            if hasattr(self, 'select_button'):
                self.select_button.configure(text=self.get_text("select_ppt"))
            if hasattr(self, 'adjust_button'):
                self.adjust_button.configure(text=self.get_text("adjust_settings"))
            if hasattr(self, 'skip_button'):
                self.skip_button.configure(text=self.get_text("skip_settings"))
            if hasattr(self, 'generate_button'):
                self.generate_button.configure(text=self.get_text("generate_video"))
            if hasattr(self, 'play_button'):
                self.play_button.configure(text=self.get_text("preview_and_play"))
            if hasattr(self, 'reselect_button'):
                self.reselect_button.configure(text=self.get_text("reselect_ppt"))

    def get_translation(self):
        # Return cached translations if available
        if self.current_language in self._translations_cache:
            return self._translations_cache[self.current_language]
            
        # Define the path to the translation file for the current language
        locale_dir = resource_path(os.path.join('pptflow', 'locales', self.current_language))
        translation_file = os.path.join(locale_dir, 'messages.json')
        
        # If the translation file doesn't exist, try to fallback to English
        if not os.path.exists(translation_file):
            logger.warning(f"Translation file not found for language: {self.current_language}")
            if self.current_language != 'en' and 'en' in self.language_modes:
                logger.info(f"Falling back to English translations")
                self.current_language = 'en'
                return self.get_translation()
            elif self.language_modes:  # Fallback to first available language
                logger.warning(f"English not available, falling back to {self.language_modes[0]}")
                self.current_language = self.language_modes[0]
                return self.get_translation()
            else:  # No translations available
                logger.error("No translation files found!")
                return {}
        
        try:
            # Load the translation file
            with open(translation_file, 'r', encoding='utf-8') as f:
                translations = json.load(f)
                
            # Ensure all required keys exist, fallback to English if missing
            if self.current_language != 'en' and 'en' in self.language_modes:
                en_translations = self._get_english_translations()
                for key, value in en_translations.items():
                    if key not in translations or not translations[key]:
                        translations[key] = value
                        logger.warning(f"Missing translation for key: {key}, using English fallback")
            
            # Cache and return the translations
            self._translations_cache[self.current_language] = translations
            return translations
            
        except Exception as e:
            logger.error(f"Error loading translation file {translation_file}: {e}")
            if self.current_language != 'en' and 'en' in self.language_modes:
                self.current_language = 'en'
                return self.get_translation()
            return {}
    
    def _get_english_translations(self):
        """Helper method to get English translations for fallback"""
        if 'en' in self._translations_cache:
            return self._translations_cache['en']
            
        en_translation_file = resource_path(os.path.join('pptflow', 'locales', 'en', 'messages.json'))
        try:
            with open(en_translation_file, 'r', encoding='utf-8') as f:
                translations = json.load(f)
                self._translations_cache['en'] = translations
                return translations
        except Exception as e:
            logger.error(f"Error loading English translations: {e}")
            return {}

    def clear_temp_cache(self):
        self.clear_audio_cache()
        self.clear_image_cache()
        logger.info("Clear temp cache")

    def clear_audio_cache(self):
        if not os.path.exists(self.setting.audio_dir_path):
            return
        
        # 获取视频文件名前缀（不含扩展名）
        video_prefix = os.path.splitext(os.path.basename(self.setting.video_path))[0]

        # 遍历目录中的文件
        for filename in os.listdir(self.setting.audio_dir_path):
            # 拼接完整路径
            file_path = os.path.join(self.setting.audio_dir_path, filename)

            # 判断是否是文件且以指定前缀开头
            if os.path.isfile(file_path) and filename.startswith(video_prefix):
                try:
                    os.remove(file_path)
                    logger.info(f"Deleted audio file: {filename}")
                except Exception as e:
                    logger.error(f"Failed to delete {filename}: {e}")

        logger.info(f"Cleared audio files with prefix: {video_prefix}")

    def clear_image_cache(self):
        if os.path.exists(self.setting.image_dir_path):
            shutil.rmtree(self.setting.image_dir_path)
        logger.info("Clear image cache")

    def on_button_click(self, label_text):
        logger.info(f"Button clicked! Text:{label_text}")
        if label_text == "Select PPT File":
            self.browse_file()
        elif label_text == "Cancel File":
            self.reselect_file()
        elif label_text == "Adjust Settings":
            self.select_frame("Adjust Settings")
        elif label_text == "Skip Settings":
            self.step += 1
            self.adjust_button.grid_remove()
            self.skip_button.grid_remove()
            self.setting_flow_1(1)
            self.cancel_settings.grid()
            self.generation_flow_2(2)
        elif label_text == "Cancel Settings":
            self.step = 1
            self.cancel_settings.grid_remove()
            self.reselect_button.grid_remove()
            self.setting_flow_1(1)
            self.generation_flow_2(2)
            self.review_flow_3(3)
        elif label_text == "Generate Video":
            threading.Thread(target=self.generate_video).start()
        elif label_text == "Preview and Play":
            self.play_video()
        elif label_text == "Open Directory":
            self.open_video_directory()
        elif label_text == "Reselect PPT":
            # 添加确认对话框
            confirm = messagebox.askyesno(
                "Confirmation",
                "Are you sure you want to reselect the PPT file?",
                parent=self  # 确保对话框居中在父窗口
            )
            self.update()
            if not confirm:
                logger.info("Reselect PPT operation cancelled by user")
                return
            self.step = 0
            self.file_display = ""
            self.create_workflow_section()
        elif label_text == "Website":
            import webbrowser
            logger.info("Loaded webbrowser")
            webbrowser.open("https://pptflow.com")

    def browse_file(self):
        self.file_display = filedialog.askopenfilename(
            filetypes=[  # 添加PDF支持
                ("Supported files", "*.pptx;*.pdf"),
                ("PowerPoint files", "*.pptx"),
                ("PDF files", "*.pdf")
            ]
        )
        if self.file_display:
            logger.info(f"Selected file: {self.file_display}")

            file_ext = os.path.splitext(self.file_display)[1].lower()

            # 仅当选择PPT文件时检查备注
            if file_ext == '.pptx' and not self.check_ppt_notes_only_english(self.file_display):
                return
            elif file_ext == '.pdf':
                self.setting.has_notes = False

            # Set the default output path
            # 设置输出路径（支持PPT和PDF扩展名替换）
            self.setting.video_path = re.sub(
                r"(pptx?|pdf)$",  # 修改正则表达式匹配PPT和PDF
                self.setting.video_format.lower(),
                self.file_display
            )
            logger.info(f"video_path:{self.setting.video_path}")

            self.file_label.grid()
            self.file_label.configure(state=ctk.NORMAL)
            # 显示文件名
            display_text = truncate_text(os.path.basename(self.file_display), max_length=10)
            self.tooltip = CustomTooltip(self.file_label, os.path.basename(self.file_display))
            self.file_label_var.set(display_text)
            self.file_label.configure(state=ctk.DISABLED)
            self.cancel_file.grid()

            self.select_button.grid_remove()
            self.step += 1
            self.setting_flow_1(1)
            
            # Ensure we're using the correct TTS provider from settings
            tts_provider = self.setting.tts_service_provider
            logger.info(f"Loading TTS service: {tts_provider}")
            self.tts = self.tts if self.tts else self.load_tts(tts_provider)

    def check_ppt_notes_only_english(self, ppt_file_path):
        from pptx import Presentation
        try:
            # ================ 新增校验逻辑 ================
            presentation = Presentation(ppt_file_path)

            # 1. 检查是否有幻灯片
            if len(presentation.slides) == 0:
                messagebox.showerror("Error", "The PPT has no slides, please reselect!")
                logger.error("The PPT has no slides, please reselect!")
                self.reselect_file()
                return False

            # 2. 遍历所有幻灯片检查备注
            for slide in presentation.slides:
                notes = slide.notes_slide.notes_text_frame.text.strip()

                # 只要有一页存在非空备注，就将has_notes设为True
                if notes:
                    self.setting.has_notes = True  # 标记存在备注
                    # 3. 中文检测逻辑
                    for char in notes:
                        if '\u4e00' <= char <= '\u9fff':
                            self.setting.language = 'zh'
                            self.setting.subtitle_length = 24
                            logger.info("Found Chinese characters in notes. Switch language to zh.")
                            return True
                        else:
                            self.setting.language = 'en'
                            self.setting.subtitle_length = 12
                            logger.info("Found English characters in notes. Switch language to en.")
                            return True
            return True
        except Exception as e:
            messagebox.showerror("Error", f"Unable to open PPT file: {e}")
            logger.error(f"Unable to open PPT file: {e}", exc_info=True)
            self.reselect_file()
            return False

    def select_frame(self, name):
        self.flow_frame.grid_remove()
        if name == "Adjust Settings":
            from .adjust_settings import AdjustSettingsFrame

            # 显示 ExportSection
            self.adjust_settings = AdjustSettingsFrame(self, self.main_frame)
            self.adjust_settings.grid(row=1, column=0, padx=(60, 0), sticky="nsew")
            self.adjust_settings.tkraise()
            self.adjust_settings.grab_set()
        elif name == "Skip Settings":
            self.next_step()
        elif name == "System Settings":
            if self.adjust_settings is not None:
                self.adjust_settings.grid_remove()
            self.flow_frame.grid_remove()
            from .system_settings import SystemSettingsFrame
            self.system_settings = SystemSettingsFrame(self, self.main_frame)
            self.system_settings.grid(row=1, column=0, padx=(100, 40), pady=(40, 60), sticky="nsew")
            self.system_settings.tkraise()
            self.system_settings.grab_set()

    def load_tts(self, tts_service_provider):
        # import tts module according to service provider
        try:
            logger.info(f"Initializing TTS service: {tts_service_provider}")
            tts_service = get_tts_service(tts_service_provider)
            
            # Get voice list from the service
            voice_list = tts_service.get_voice_list(self.setting)
            
            # Update settings based on the provider
            if tts_service_provider == "azure":
                sd.tts_speech_voices = voice_list if len(voice_list) > 0 else sd.tts_speech_voices
            elif tts_service_provider == "kokoro":
                sd.kokoro_voice_type = voice_list if len(voice_list) > 0 else sd.kokoro_voice_type
            # For cosyvoice_v2, we don't need to update any settings, but we can log the available voices
            elif tts_service_provider in ["cosyvoice_v2", "cosy-voice-v2", "cosy_voice_v2"]:
                logger.info(f"Loaded {len(voice_list)} voices for cosyvoice_v2")
                
            return tts_service.tts
            
        except Exception as e:
            logger.error(f"Error loading TTS service '{tts_service_provider}': {e}", exc_info=True)
            messagebox.showerror(
                self.get_text("error"),
                f"{self.get_text('error_loading_tts')}: {str(e)}"
            )
            return None

    def update_progress(self, progress: float, status: str):
        """Update progress bar and status label"""
        self.progress_bar.set(progress)
        # self.status_label.configure(text=status)

    def generate_video(self):
        if not self.file_display:
            messagebox.showerror(self.loading_title, self.get_text("no_file_selected"))
            return
        if not self.setting.has_notes:
            if os.path.exists(re.sub(r"(pptx?|pdf)$", 'txt', self.file_display)):
                self.setting.external_notes_path = re.sub(r"(pptx?|pdf)$", 'txt', self.file_display)
                logger.info(f"Found external notes txt file: {self.setting.external_notes_path}")
            elif os.path.exists(re.sub(r"(pptx?|pdf)$", 'docx', self.file_display)):
                self.setting.external_notes_path = re.sub(r"(pptx?|pdf)$", 'docx', self.file_display)
                logger.info(f"Found external notes docx file: {self.setting.external_notes_path}")
            else:
                messagebox.showerror(self.loading_title, self.get_text("no_notes_found"))
                logger.error(self.get_text("no_notes_found"))
                return
        else:
            logger.info("PPT has notes, no need to search for external notes.")
        try:
            # Show progress frame
            self.progress_bar.grid()
            self.generate_button.grid_remove()
            self.update()

            # Load ppt2video
            from pptflow import ppt2video
            logger.info("Loaded ppt2video")
            # Initialize progress tracker
            from pptflow.utils.progress_tracker import ProgressTracker
            logger.info("Loaded ProgressTracker")
            self.progress_tracker = ProgressTracker(self.update_progress)
            ppt2video.ppt_to_video(self.tts, self.file_display, self.setting, self.progress_tracker)

            messagebox.showinfo(self.loading_title,
                                f'{self.get_text("video_generated")}{self.setting.video_path}')
            self.step += 1
            self.generation_flow_2(2)
            self.review_flow_3(3)
            self.reselect_button.grid()
        except Exception as e:
            messagebox.showerror("Error", f"Failed to generate video: {str(e)}")
            logger.error(e, exc_info=True)
            return
        finally:
            # Hide progress frame
            self.progress_bar.grid_remove()
            self.generate_button.grid()
            self.progress_bar.set(0)

    def play_video(self):
        logger.info(f'video_path:{self.setting.video_path}')
        try:
            if not os.path.exists(self.setting.video_path):
                logger.error(f'video_path:{self.setting.video_path} does not exist!')
                raise FileNotFoundError(f'video_path:{self.setting.video_path} does not exist!')
            if sys.platform == "win32":
                os.startfile(self.setting.video_path)
            else:
                import subprocess
                logger.info("Loaded subprocess")
                opener = "open" if sys.platform == "darwin" else "xdg-open"
                subprocess.call([opener, self.setting.video_path])
        except Exception as e:
            messagebox.showerror("Error", "No video was generated!")
            logger.error(e, exc_info=True)

    def reselect_file(self):

        self.step = 0
        self.file_label_var.set("")
        self.tooltip = None
        self.setting.video_path = ""
        self.cancel_file.grid_remove()
        self.create_workflow_section()

    def open_video_directory(self):
        if not self.setting.video_path:
            messagebox.showerror("Error", "Video path not specified")
            return

        directory = os.path.dirname(self.setting.video_path)

        try:
            if not os.path.exists(directory):
                raise FileNotFoundError(f"Directory not found: {directory}")

            if platform.system() == "Windows":
                # Windows 使用 explorer
                os.startfile(directory)
            else:
                import subprocess
                logger.info("Loaded subprocess")
                opener = "open" if sys.platform == "darwin" else "xdg-open"
                subprocess.call([opener, directory])

        except Exception as e:
            messagebox.showerror("Error", f"Failed to open directory: {str(e)}")
            logger.error(f"Open directory failed: {e}", exc_info=True)
        pass

    def load_ctk_image(self, file_name, size):
        try:
            icon_dir = resource_path(os.path.join("assets", "icons"))
            file_name = os.path.join(icon_dir, file_name)
            # 加载并调整图标大小
            if type(size) == int:
                size = (size, size)
            img = Image.open(file_name)
            img = img.resize(size, Image.LANCZOS)  # 替换 ANTI_ALIAS 为 LANCZOS
            return ctk.CTkImage(light_image=img, dark_image=img, size=size)  # 使用 CTkImage
        except Exception as e:
            print(f"Error loading icon {file_name}: {e}")
            return None

    def update_language(self):
        if self.select_button:
            self.select_button.configure(text=self.get_text("select_ppt"))
        if self.cancel_file:
            self.cancel_file.configure(text=self.get_text("cancel"))
        self.adjust_button.configure(text=self.get_text("adjust_settings"))
        self.skip_button.configure(text=self.get_text("skip_settings"))
        if self.cancel_settings:
            self.cancel_settings.configure(text=self.get_text("cancel"))
        if self.generate_button:
            self.generate_button.configure(text=self.get_text("generate_video"))
        self.play_button.configure(text=self.get_text("preview_and_play"))
        self.reselect_button.configure(text=self.get_text("reselect_ppt"))


def get_locales_subdirectories():
    # Get the absolute path to the current file
    current_file_path = os.path.abspath(__file__)
    # Get the directory path of the current file
    current_directory = os.path.dirname(current_file_path)
    # Get the parent path of the current directory
    parent_directory = os.path.dirname(current_directory)
    # Get the absolute path of the parent directory
    language_locales_path = os.path.join(parent_directory, 'locales')
    try:
        # Get all entries in the directory
        entries = os.listdir(language_locales_path)
        # Filter out subdirectories
        subdirectories = [
            entry for entry in entries
            if os.path.isdir(os.path.join(language_locales_path, entry)) and entry != '__pycache__'
        ]
        return subdirectories
    except FileNotFoundError:
        logger.error(f"Directory {language_locales_path} doesn't exist!")
        return []
    except PermissionError:
        logger.error(f"No permission to access the directory {language_locales_path}")
        return []


def truncate_text(text, max_length=20):
    """ 截取文本，确保头部+省略号+尾部不超过 max_length """
    if len(text) > max_length:
        return text[:max_length // 2] + "..." + text[-(max_length // 2):]
    return text


if __name__ == "__main__":
    app = PPTFlowApp()
    app.mainloop()
